import json
import os
import socket
import struct
from typing import Type

from app.core.conf import ST
from app.tornado.limit import Tornado
from loguru import logger

connectTornado = Tornado("connect", 3, 10 * 60)


class AGV:
    __slots__ = ("socket_pool", "ip", "port")

    agv = {}

    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.socket_pool = {}
        self.init_socket_pool()

    def init_socket_pool(self):
        # if not self.ping_agv():
        #     return

        if self.socket_pool:  # 如果socket pool,已经初始化,不为空,则返回
            return

        for name, port in self.port.items():
            self.socket_pool[name] = self.connect(port, name)

    def send(self, name, req_id, msg_type, msg={}):
        raw_msg = AGV.pack_msg(req_id, msg_type, msg)
        try:
            self.socket_pool.get(name).send(raw_msg)
        except Exception as e:
            logger.error("向AGV发送消息失败,可能5G网络断开,也可能是通信延迟")
            # 说明网络可能断电,所以要重新连接,所以清空原来的通讯池
            self.socket_pool = {}
            # 测试测试是否联通,如果联不通,则
            self.init_socket_pool()

    def send_raw(self, name, raw):
        try:
            self.socket_pool.get(name).send(raw)
        except Exception as e:
            logger.error("向AGV发送消息失败,可能5G网络断开,也可能是通信延迟")
            # 说明网络可能断电,所以要重新连接,所以清空原来的通讯池
            self.socket_pool = {}
            # 测试测试是否联通,如果联不通,则
            self.init_socket_pool()

    def receive(self, name):
        so = self.socket_pool.get(name)
        global data
        data_all = b""
        try:
            data = so.recv(16)  # 接受16个字节的数据
            so.settimeout(10)
        except socket.timeout:
            logger.error("receive time out")
            so.close()
            self.socket_pool = {}
            self.init_socket_pool()

        if len(data) < 16:  # 0-15,刚好16
            logger.error("pack head error")
            so.close()
            self.socket_pool = {}
            self.init_socket_pool()
            return

        json_data_len = 0
        try:
            header = struct.unpack("!BBHLH6s", data)
        except Exception as e:
            logger.debug("接受字节长度出错,无法解包")
            so.close()
            self.socket_pool = {}
            self.init_socket_pool()
            return

        json_data_len = header[3]
        data_all += data
        data = b""
        read_size = 1024

        try:
            while json_data_len > 0:
                recv = so.recv(read_size)
                data += recv
                json_data_len -= len(recv)
                if json_data_len < read_size:
                    read_size = json_data_len
            data_all += data
            return json.loads(data_all[16:].decode())
        except socket.timeout:
            so.close()
            self.socket_pool = {}
            self.init_socket_pool()
            return

    @staticmethod
    def pack_msg(req_id, msg_type, msg={}):
        msg_len = 0
        json_str = json.dumps(msg)

        if msg != {}:
            msg_len = len(json_str)

        raw_msg = struct.pack("!BBHLH6s", 0x5A, 0x01, req_id, msg_len, msg_type, b"\x00\x00\x00\x00\x00\x00")
        if msg != {}:
            raw_msg += bytearray(json_str, "ascii")

        return raw_msg

    def connect(self, port, name=None):
        so = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            so.connect((self.ip, port))
            so.settimeout(1)
            logger.info(f"{port} to agv ok!")
            return so
        except Exception as e:
            logger.error(f"{port} to agv fail!")
            # 关闭连接
            so.close()
            # 打印异常
            logger.error(e)
            return None

    @classmethod
    def ping_agv(cls, ip):
        exit_code = os.system(f"ping {ip}")
        if exit_code == 0:
            logger.info(f"{ip}5G网络畅通")
            return True
        else:
            return False

    @classmethod
    def get_agv(cls, ip, port=None) -> Type["AGV"]:
        if port is None:
            if cls.agv.get(ip) is None:
                try:
                    cls.agv[ip] = cls(ip, port)
                except Exception as e:
                    logger.warning(e.args)
            return cls.agv.get(ip)
        else:
            try:
                cls.agv[ip] = cls(ip, port)
            except Exception as e:
                logger.warning(e.args)
            if cls.agv.get(ip) is None:
                return None
            return cls.agv.get(ip)


if __name__ == "__main__":
    tt_agv = AGV.get_agv(ST.AGV1_IP, ST.AGV_PORT)
    print(AGV.agv)
    tt_agv.send("status", 1, 1300)
    map_info = tt_agv.receive("status")
    print(map_info)
